#include "StdAfx.h"
#ifdef WIN32
//# include <windows.h>
#endif
#include "Debug.h"
#include <malloc.h>
#include <algorithm>

#include <wx/filename.h>

#include "yasli/Files.h"

#include "TagSourceDirectory.h"
#include "TagStorage.h"
#include "TagPreferences.h"

#include "Serialization.h"
#include "yasli/TypesFactory.h"

class TagSourceDirectory;

class TagSourceNode : public RefCounter
{
public:
    TagSourceNode()
    : parent_(0)
	, source_(0)
    {
    }
	virtual ~TagSourceNode() {}
    virtual void poll( TagStorage* storage ) = 0;
	void setName(const char* name){ name_ = name; }
	void setSource(TagSourceDirectory* source){ source_ = source; }
	TagSourceDirectory* source(){ return source_; }
	const char* name() const{ return name_.c_str(); }
	const char* path() const{ return path_.c_str(); }

    virtual void setParent(TagSourceNode* parent){ parent_ = parent; }
    TagSourceNode* parent(){ return parent_; }
	void add(TagSourceNode* newChild);
	void remove(TagSourceNode* node);
	TagSourceNode* findByName(const char* name);
    virtual bool rescan() { return false; }

    void serialize(Archive& ar);
protected:
    TagSourceNode* parent_;

	TagSourceDirectory* source_;
	typedef std::vector< SharedPtr<TagSourceNode> > Children;
	Children children_;

    string name_;
    string path_;
};

class TagSourceNodeDirectory : public TagSourceNode, public DirectoryWatch{
public:
    TagSourceNodeDirectory();

	bool onFileAdded(const char* name);
	bool onFileRemoved(const char* name);
	bool onFileRenamed(const char* oldName, const char* newName);
	bool onFileModified(const char* name);

	bool attach(const char* path, bool root = false);
	bool reattach(bool root = false);
	void poll( TagStorage* storage );

    void setParent(TagSourceNode* parent);
	TagSourceNodeDirectory* parent(){ return safe_cast<TagSourceNodeDirectory*>(TagSourceNode::parent()); }

	void serialize(Archive& ar);

	void setSource(TagSourceDirectory* source);
protected:
};

class TagSourceNodeFile : public TagSourceNode{
public:
    TagSourceNodeFile(TagSourceNodeDirectory* parent = 0);
	~TagSourceNodeFile();
	TagSourceNodeDirectory* parent() { return static_cast<TagSourceNodeDirectory*>(TagSourceNode::parent()); }
	bool attach(const char* path);
	bool rescan();

	bool parseSource(const char* sourceFileName);
	void serialize(Archive& ar);
    void poll( TagStorage* storage );
protected:
	Files::time_t timeStamp_;
    SharedPtr<TagBlock> block_;
    SharedPtr<TagBlock> newBlock_;
};

// ---------------------------------------------------------------------------


void TagSourceNode::add(TagSourceNode* newChild)
{
	children_.push_back(newChild);
	newChild->setParent(this);
}

void TagSourceNode::remove(TagSourceNode* node)
{
	Children::iterator it = std::find(children_.begin(), children_.end(), node);
	if(it != children_.end()){
		node->setParent(0);
		children_.erase(it);
	}
	else
		ASSERT(0 && "No such node");
}

TagSourceNode* TagSourceNode::findByName(const char* name)
{
	Children::iterator it;
	FOR_EACH(children_, it){
		TagSourceNode* child = *it;
		if(child && strcmp(child->name(), name) == 0)
			return child;
	}
	return 0;
}

void TagSourceNode::serialize(Archive& ar)
{
	ar(name_, "name");
	ar(children_, "children");
	if(ar.isInput()){
		Children::iterator it;
		for(it = children_.begin(); it != children_.end(); ){
			TagSourceNode* node = *it;
			if(node){
				node->setParent(this);
				++it;			
			}
			else
				it = children_.erase(it);
		}
	}
}

// ---------------------------------------------------------------------------

SERIALIZATION_DERIVED_TYPE(TagSource, TagSourceDirectory, "TagSourceDirectory")
SERIALIZATION_DERIVED_TYPE(TagSourceNode, TagSourceNodeDirectory, "TagSourceNodeDirectory")
SERIALIZATION_DERIVED_TYPE(TagSourceNode, TagSourceNodeFile, "TagSourceNodeFile")

#pragma warning(push)
#pragma warning(disable : 4355) // 'this' : used in base member initializer list
TagSourceDirectory::TagSourceDirectory(TagStorage* storage, const char* path)
: TagSource(storage)
, directoryWatch_(new DirectoryWatch(this))
, changed_(false)
, directory_(new TagSourceNodeDirectory())
{
	directory_->setSource(this);
	if(path){
		path_ = path;
		tagFilePath_ = path;
		tagFilePath_ += Files::PATH_SEPARATOR;
		tagFilePath_ += "tags";

		directory_->attach(path, true);
	}
}
#pragma warning(pop)

TagSourceDirectory::~TagSourceDirectory()
{
	directoryWatch_->uninstall();
	delete directoryWatch_;
}

void TagSourceDirectory::rescan()
{
}

void TagSourceDirectory::serialize(Archive& ar)
{
	TagSource::serialize(ar);
	ar(*directory_, "directory");
}


void TagSourceDirectory::setStorage(TagStorage* storage)
{
	TagSource::setStorage(storage);
	directory_->setListener(this);
	directory_->setSource(this);
}

void TagSourceDirectory::reattach()
{
	directory_->reattach(true);
}

TagSourceNode* TagSourceDirectory::findNodeByPath(const char* path, bool filePath)
{
	size_t len = strlen(path);
	const char* end = path + len;
	const char* p = path;
	const char* lastSep = path;
	
	TagSourceNode* currentNode = directory_;
	while(true){
		while(p != end && *p != Files::PATH_SEPARATOR[0])
			++p;
		if(filePath && p == end)
			return currentNode;
		else if(TagSourceNode* node = currentNode->findByName(std::string(lastSep, p).c_str())){
			currentNode = node;
			if(p == end)
				return currentNode;
			else{
				++p;
				lastSep = p;
			}
		}
		else
			return 0;
	}
}

void TagSourceDirectory::onFileAdded(DirectoryWatch* watch, const char* path)
{
	if(TagSourceNodeDirectory* dir = safe_cast<TagSourceNodeDirectory*>(findNodeByPath(path)))
		if(dir->onFileAdded(Files::extractFileName(path)))
			changed_ = true;

}

void TagSourceDirectory::onFileRemoved(DirectoryWatch* watch, const char* path)
{
	if(TagSourceNodeDirectory* dir = safe_cast<TagSourceNodeDirectory*>(findNodeByPath(path)))
		if(dir->onFileRemoved(Files::extractFileName(path)))
			changed_ = true;
}


void TagSourceDirectory::onFileModified(DirectoryWatch* watch, const char* path)
{
	if(TagSourceNodeDirectory* dir = safe_cast<TagSourceNodeDirectory*>(findNodeByPath(path)))
		if(dir->onFileModified(Files::extractFileName(path)))
			changed_ = true;
}

void TagSourceDirectory::onFileRenamed(DirectoryWatch* watch, const char* oldName, const char* newName)
{
	if(TagSourceNodeDirectory* dir = safe_cast<TagSourceNodeDirectory*>(findNodeByPath(oldName)))
		if(dir->onFileRenamed(Files::extractFileName(oldName), Files::extractFileName(newName)))
			changed_ = true;
}

void TagSourceDirectory::poll( TagStorage* storage )
{
	directory_->poll( storage );
}

// ---------------------------------------------------------------------------


TagSourceNodeDirectory::TagSourceNodeDirectory()
{
}



void TagSourceNodeDirectory::serialize(Archive& ar)
{
	ar(DirectoryWatch::path_, "path");
	TagSourceNode::serialize(ar);
}

void TagSourceNodeDirectory::setParent(TagSourceNode* parent)
{
	TagSourceNode::setParent(parent);
	ASSERT(parent->source() != 0);
	if(parent->source() != 0)
		setSource(parent->source());
}

void TagSourceNodeDirectory::setSource(TagSourceDirectory* source)
{
	TagSourceNode::setSource(source);
	ASSERT(source);
	DirectoryWatch::setListener(source);
}


void TagSourceNodeDirectory::poll( TagStorage* storage )
{
	ASSERT(listener_ != 0);
    bool changed = DirectoryWatch::poll();
	Children::iterator it;
	for(it = children_.begin(); it != children_.end(); ++it)
	{
		TagSourceNode* node = *it;
		node->poll( storage );
	}
}

bool TagSourceNodeDirectory::reattach(bool root)
{
	return attach(DirectoryWatch::path_.c_str(), root);
}

bool TagSourceNodeDirectory::attach(const char* path, bool root)
{
	if(!Files::isDirectory(path)){
		std::cout << "Unable to attach path, is not a directory: " << path << std::endl;
		return false;
	}
	if(root){
		if(!install(path, true)){
			std::cout << "Unable to install DirectoryWatch on " << path << std::endl;
			return false;
		}
	}
	DirectoryWatch::path_ = path;
	name_ = Files::extractFileName(path);

	std::cout << "Looking for files in " << path << std::endl;
	std::string mask = path;
	mask += Files::PATH_SEPARATOR;
	mask += "*";
	Files::iterator it(mask.c_str()), end;
	for(; it != end; ++it){
		std::cout << " * got file " << it->name() << std::endl;
		onFileAdded(it->name());
	}
	return true;
}

bool TagSourceNodeDirectory::onFileAdded(const char* name)
{
	std::string fullPath = DirectoryWatch::path_ + Files::PATH_SEPARATOR + name;

	if(Files::isDirectory(fullPath.c_str())){
		if(tagPreferences.skipDirectory(name))
			return false;
		
		TagSourceNodeDirectory* node = dynamic_cast<TagSourceNodeDirectory*>(findByName(name));
		if(node){
			node->setParent(this);
			if(!node->attach(fullPath.c_str(), false))
				return false;
		}
		else{
			TagSourceNodeDirectory* node = new TagSourceNodeDirectory();
			node->setParent(this);
			if(!node->attach(fullPath.c_str(), false))
				delete node;
			else
				add(node);
		}
		return true;
	}
	else if (tagPreferences.shallBeScanned(name)){
		TagSourceNodeFile* node = dynamic_cast<TagSourceNodeFile*>(findByName(name));
		if(!node){
			node = new TagSourceNodeFile(this);
			if(!node->attach(fullPath.c_str()))
				delete node;
			else
				add(node);
		}
		else{
			if(!node->attach(fullPath.c_str()))
				remove(node);
		}
		return true;
	}
	return false;
}

bool TagSourceNodeDirectory::onFileRemoved(const char* name)
{
	if(TagSourceNode* node = findByName(name)){
		remove(node);
	}
	return true;
}

bool TagSourceNodeDirectory::onFileRenamed(const char* oldName, const char* newName)
{
	if(TagSourceNode* node = findByName(oldName))
    {
        ASSERT(0 && "Implement me");
		//node->rename(newName);
    }
	else
		onFileAdded(newName);
	return true;
}

bool TagSourceNodeDirectory::onFileModified(const char* name)
{
	if(TagSourceNode* node = findByName(name))
		node->rescan();
	else
		onFileAdded(name);
	return true;
}

// ---------------------------------------------------------------------------

TagSourceNodeFile::TagSourceNodeFile(TagSourceNodeDirectory* directory)
: timeStamp_(0)
{
	if(directory)
		setParent(directory);
}

void TagSourceNodeFile::serialize(Archive& ar)
{
	ar(path_, "path");
	ar(timeStamp_, "timeStamp");
	TagSourceNode::serialize(ar);
}

bool TagSourceNodeFile::attach(const char* path)
{
	if(path[0] == '\0')
		return false;

	if(path_ == path){
		Files::time_t fileTime = Files::getModifyTime(path_.c_str());
		if(fileTime > timeStamp_)
			return TagSourceNode::rescan();
	}
	else{
		name_ = Files::extractFileName(path);
		path_ = path;

		return TagSourceNodeFile::rescan();
	}
	return false;
}

void TagSourceNodeFile::poll( TagStorage* storage )
{
    if( newBlock_ ){
        if(block_)
            block_->setDead(true);
        block_ = newBlock_;
        newBlock_ = 0;
        storage->addBlock(block_);
    }
}

TagSourceNodeFile::~TagSourceNodeFile()
{
    if( block_ )
        block_->setDead( true );
}

bool TagSourceNodeFile::rescan()
{
	std::string msg = " rescaning file ";
	msg += path_.c_str();
	msg += "\n";
	TRACE(msg.c_str());
	if(timeStamp_ < Files::getModifyTime(path_.c_str()))
        return parseSource( path_.c_str() );
	else{
		TRACE("  mtime is the same: skipping reparse\n");
		return true;
	}
}

bool TagSourceNodeFile::parseSource(const char* sourceFileName)
{
	timeStamp_ = Files::getModifyTime(sourceFileName);
	if(timeStamp_ == 0)
		return false;

	wxString tempFileName = wxFileName::CreateTempFileName(wxString("vimkick", wxConvUTF8));
	std::string commandLine = tagPreferences.ctagsExecutable();
	commandLine += " --file-scope=yes --excmd=number --c++-kinds=+p --fields=afikmnSzts --sort=no -f \"";

	commandLine += tempFileName.utf8_str();
	commandLine += "\" \"";
	commandLine += sourceFileName;
	commandLine += "\"";

#ifdef WIN32
	STARTUPINFO startupInfo;
	GetStartupInfo(&startupInfo);

	// temproary solution. shall be implemented with pipes
	PROCESS_INFORMATION processInfo;
	ZeroMemory(&processInfo, sizeof(processInfo));
	
	char* commandLineBuffer = (char*)alloca(commandLine.size() + 1);
	strcpy(commandLineBuffer, commandLine.c_str());
	CreateProcess(0, commandLineBuffer, 0, 0, FALSE, CREATE_NO_WINDOW, 0, 0, &startupInfo, &processInfo);
	
	WaitForSingleObject(processInfo.hProcess, INFINITE);
	CloseHandle(processInfo.hProcess);
	CloseHandle(processInfo.hThread);
#else
	std::cout << "Parsing " << sourceFileName << std::endl;
	system(commandLine.c_str());
#endif
	
    newBlock_ = new TagBlock();
	bool result = newBlock_->loadFromFile(tempFileName.utf8_str());;
	Files::remove(tempFileName.utf8_str());
	return result;
}

// ---------------------------------------------------------------------------

TagSourceFileList::TagSourceFileList(TagStorage* storage)
: TagSource(storage)
{

}

void TagSourceFileList::add(const char* path)
{
    newFiles_.push_back(path);
}

void TagSourceFileList::remove(const char* path)
{
    removedFiles_.push_back(path);
}

void TagSourceFileList::removeNode(const char* path)
{
    TagSourceNodeFiles::iterator fit;
    for(fit = files_.begin(); fit != files_.end();){
        TagSourceNodeFile* node = fit->get();
        if(strcmp(node->path(), path) == 0 )
            fit = files_.erase(fit);
        else
            ++fit;
    }
}

void TagSourceFileList::poll(TagStorage* storage)
{
	bool changed = false;
	ASSERT(storage);
    vector<string>::iterator it;
    FOR_EACH(newFiles_, it){
        const char* path = it->c_str();
        SharedPtr<TagSourceNodeFile> node(new TagSourceNodeFile());
		//node->setStorage(storage);
        if(node->attach(path))
            files_.push_back(node);
    }
	if(!newFiles_.empty())
		changed = true;	
    newFiles_.clear();

    FOR_EACH(removedFiles_, it){
        const char* path = it->c_str();
        removeNode(path);
    }
	if(!removedFiles_.empty())
		changed = true;	
    removedFiles_.clear();

	{
		TagSourceNodeFiles::iterator it;
		for(it = files_.begin(); it != files_.end(); ++it){
			TagSourceNodeFile* node = *it;
			node->poll(storage);
		}
	}
	//return changed;
}


